//------------------------------------------------------------
// Microsoft?XNA Game Studio Creator's Guide  
// by Stephen Cawood and Pat McGee 
// Copyright (c) McGraw-Hill/Osborne. All rights reserved.
// http://www.mhprofessional.com/product.php?isbn=007149071X
//------------------------------------------------------------
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using NS_Camera;
using System.Runtime.InteropServices;
#endregion
using quakeMD2;

namespace MGHGame
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        //------------------------------------------------------------
        // Declarations for handling mouse on the PC
        //------------------------------------------------------------
#if !XBOX
        MouseState mMouse;        // store Mouse X and Y

        // dll ref to use API for setting and getting cursor pos
        [DllImport("user32.dll")] 
        static extern bool SetCursorPos(int X, int Y);
        [DllImport("user32.dll")]
        static extern int GetCursorPos(ref tPoint lpPoint);

        // stores cursor X and Y as integer
        struct tPoint { public int X, Y; }

        // converts to cursor location coordinates to integers
        public static Point GetCursorPoint()
        {
            tPoint point;
            point.X = 0; point.Y = 0;
            GetCursorPos(ref point);
            return new Point(point.X, point.Y);
        }
#endif
        //------------------------------------------------------------
        // M O D U L E   D E C L A R A T I O N S
        //------------------------------------------------------------
        // constant definitions
        private const float BOUNDARY = 16.0f;

        GraphicsDeviceManager   gfx;
        ContentManager          content;

        // shader object - BasicShader.fx
        private Effect          mfx;                    // shader object                 
        private EffectParameter worldViewProjParam;     // cumulative matrix w*v*p (use this order)
        // sets wvp in shader

        // shader object - TexturedShader.fx
        private Effect          mfxTex;                 // shader object                 
        private EffectParameter mfxTex_WVP;             // cumulative matrix w*v*p 
        private EffectParameter mfxTexture;             // texture param

        // camera 
        private CCamera         cam = new CCamera();
        private Matrix          mMatWorld;
        private Matrix          mMatView;
        private Matrix          mMatProj;

        // input devices

        private GamePadState[]  mGamePadState = new GamePadState[4];

        // vertex types and buffers
        private VertexDeclaration               mVertPosColor;
        private VertexDeclaration               mVertPosColorTex;
        private VertexPositionColorTexture[]    mVertGround = new VertexPositionColorTexture[4];
        private Texture2D                       mTexGrass;
        private MD2                             md2;
        private MD2                             md2Weapon;

        private double                          mdblAnimDelay, mdblRunDelay, mdblJumpDelay = 0;

        public enum meAnim
        {
            stand, run, attack, pain1,
            pain2, pain3, jump, flip,
            salute, taunt, wave, point,
            crstand, crwalk, crattack, crpain,
            crdeath, death1, death2, death3
        }
        const int                               CHARACTER   = 0;
        const int                               WEAPON      = 1;
        

        BasicEffect                             mBE;
        private VertexDeclaration               mVertPosNormTex;

        /// <summary>
        /// -initializes GraphicsDeviceManager object for drawing 
        /// -initializes ContentManager object for loading media
        /// </summary>
        public Game1()
        {
            gfx     = new GraphicsDeviceManager(this);
            content = new ContentManager(Services);
        }

        /// <summary>
        /// Moves camera forwards and backwards when
        /// left thumbstick shifted up or down.
        /// Also moves camera when UP/DOWN arrow keys pressed.
        /// </summary>
        float move()
        {
            KeyboardState kbState   = Keyboard.GetState();
            GamePadState  gpState   = getNewState(mGamePadState[0]);
            float         fMove     = 0.0f;
            const float   kScale    = 1.50f;

            if (mGamePadState[0].IsConnected)   // left stick shifted left / right
            {
                if (gpState.ThumbSticks.Left.Y != 0.0f)
                    fMove = (kScale * gpState.ThumbSticks.Left.Y);
            }
            else                                // no gamepad - use UP / DOWN keys
                if (kbState.IsKeyDown(Keys.Up))         // UP   - move ahead
                    fMove = (1.0f);
                else if (kbState.IsKeyDown(Keys.Down))  // DOWN - move back
                    fMove = (-1.0f);
            return fMove;
        }

        /// <summary>
        /// Moves camera sideways when left thumbstick shifted left or 
        /// right. Also moves camera when RIGHT/LEFT arrow keys pressed.
        /// </summary>
        float strafe()
        {
            KeyboardState kbState = Keyboard.GetState();
            GamePadState  gpState = getNewState(mGamePadState[0]);

            // using gamepad leftStick shifted left / right for strafe
            if (mGamePadState[0].IsConnected)
            { 
                if(gpState.ThumbSticks.Left.X != 0.0f)
                    return gpState.ThumbSticks.Left.X;
            }
            // using keyboard - strafe with X and Z keys
            else if (kbState.IsKeyDown(Keys.Left)) // strafe left
                return -1.0f;
            else if (kbState.IsKeyDown(Keys.Right)) // strafe right
                return 1.0f;
            return 0.0f;
        }

        /// <summary>
        /// Returns latest press, release, and shift states for
        /// each game controller.
        /// </summary>
        /// <param name="state">Stores press, release, and shift states for controller.</param>
        GamePadState getNewState(GamePadState state)
        {
            return state;
        }

        /// <summary>
        /// Refreshes press, release, and shift states for each controller.
        /// </summary>
        private void UpdateGamePad()
        {
            mGamePadState[0] = GamePad.GetState(PlayerIndex.One);
            mGamePadState[1] = GamePad.GetState(PlayerIndex.Two);
            mGamePadState[2] = GamePad.GetState(PlayerIndex.Three);
            mGamePadState[3] = GamePad.GetState(PlayerIndex.Four);
        }

        /// <summary>
        /// Executes set-up routines when program begins. As a minimum
        /// it initializes shaders and vertices.
        /// </summary>
        protected override void Initialize()
        {
            // set window, shader, camera projection, grahics
            init_XNA_app();

            // set vertex format
            mVertPosColor       = new VertexDeclaration(gfx.GraphicsDevice,
                                      VertexPositionColor.VertexElements);

            mVertPosColorTex    = new VertexDeclaration(gfx.GraphicsDevice,
                                      VertexPositionColorTexture.VertexElements);
            // initialize game objects
            init_ground();

#if !XBOX
            SetCursorPos(Window.ClientBounds.Width / 2, Window.ClientBounds.Height / 2);
#endif
            md2       = new MD2();
            md2.loadModel(gfx.GraphicsDevice, ".\\Models\\tris",
                                              ".\\Images\\Zarlag", content);

            md2.setAnimSpeed(5.0f); // 5.0f is the default speed
            md2.setAnim((int)meAnim.stand);

            md2Weapon = new MD2();
            md2Weapon.loadModel(gfx.GraphicsDevice,".\\Models\\weapon", 
                                                   ".\\Images\\weaponSkin", content);
            md2Weapon.setAnimSpeed(5.0f);
            md2Weapon.setAnim((int)meAnim.stand);

            mBE             = new BasicEffect(gfx.GraphicsDevice, null);
            setBasicEffect();
            mVertPosNormTex = new VertexDeclaration(gfx.GraphicsDevice,
                              VertexPositionNormalTexture.VertexElements);


            // all code for this method must be above this line
            base.Initialize();
        }

        public void setBasicEffect()
        {
            // set up lighting
            mBE.LightingEnabled                = true;
            mBE.DirectionalLight0.Enabled      = true;
            mBE.AmbientLightColor              = new Vector3(0.99f, 0.99f, 0.99f);
            mBE.DirectionalLight0.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
            mBE.DirectionalLight0.Direction    = Vector3.Normalize(
                                                 new Vector3(0.0f, -0.3f, 1.0f));
            mBE.DirectionalLight0.SpecularColor= new Vector3(0.1f, 0.1f, 0.1f);
            mBE.SpecularPower                  = 0.01f;
            mBE.TextureEnabled                 = true;
        }

        /// <summary>
        /// Load your graphics content.  When loadAllContent is true, load content 
        /// from both ResourceManagementMode pools.  Otherwise, just
        /// load ResourceManagementMode.Manual content.
        /// </summary>
        /// <param name="loadAllContent">Which type of content to load.</param>
        protected override void LoadGraphicsContent(bool loadAllContent)
        {
            if (loadAllContent)
            {
                mTexGrass = content.Load<Texture2D>(".\\Images\\grass");
            }
        }

        /// <summary>
        /// Unload your graphics content.  If unloadAllContent is true, unload content 
        /// from both ResourceManagementMode pools.  Otherwise, just unload 
        /// ResourceManagementMode.Manual content.  
        /// </summary>
        /// <param name="unloadAllContent">Which type of content to unload.</param>
        protected override void UnloadGraphicsContent(bool unloadAllContent)
        {
            if (unloadAllContent == true)
            {
                content.Unload();
            }
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            KeyboardState kbState = Keyboard.GetState();

            // allows the default game to exit on Xbox 360 and Windows
            if (GamePad.GetState(PlayerIndex.One).Buttons.X == ButtonState.Pressed
                || kbState.IsKeyDown(Keys.Escape))
            { this.Exit(); }// Camera change      
        
            // update timer in camera to regulate camera speed
            cam.set_frame_interval(gameTime);

            // update controls
            UpdateGamePad();
#if !XBOX
            mMouse = Mouse.GetState();
#endif
            cam.move(move());
            cam.strafe(strafe());

            // update matrices that need to change each frame
            set_view_matrix(gameTime);
            gfx.GraphicsDevice.Vertices[0].SetSource( null, 0, 0 );
            md2.updateModel(gfx.GraphicsDevice, gameTime);
            md2Weapon.updateModel(gfx.GraphicsDevice, gameTime);

            if (gameTime.TotalGameTime.TotalMilliseconds - mdblAnimDelay > 200)
            {
                mdblAnimDelay = gameTime.TotalGameTime.TotalMilliseconds;
                if (kbState.IsKeyDown(Keys.Space) ||
                GamePad.GetState(PlayerIndex.One).Buttons.LeftStick
                == ButtonState.Pressed)
                {
                    md2.advanceAnimation();
                    md2Weapon.advanceAnimation();
                }
            }
            if (gameTime.TotalGameTime.TotalMilliseconds - mdblRunDelay > 100)
            {
                mdblRunDelay = gameTime.TotalGameTime.TotalMilliseconds;
                // start running animation if it isn't already playing
                if ((kbState.IsKeyDown(Keys.A)
                || GamePad.GetState(PlayerIndex.One).Triggers.Right > 0.0f)
                && !md2.isPlaying((int)meAnim.run))
                {
                    md2.setAnim((int)meAnim.run);
                    md2Weapon.setAnim((int)meAnim.run);
                }
            }
            if (gameTime.TotalGameTime.TotalMilliseconds - mdblJumpDelay > 100)
            {
                mdblJumpDelay = gameTime.TotalGameTime.TotalMilliseconds;
                // start jump animation if it isn't already playing
                if ((kbState.IsKeyDown(Keys.J) ||
                GamePad.GetState(PlayerIndex.One).Buttons.RightStick
                == ButtonState.Pressed)
                && !md2.isPlaying((int)meAnim.jump))
                {
                    md2.setAnimSequence((int)meAnim.jump, (int)meAnim.run);
                    md2Weapon.setAnimSequence((int)meAnim.jump, (int)meAnim.run);
                }
            }
            if (kbState.IsKeyDown(Keys.P) ||
                GamePad.GetState(PlayerIndex.One).Buttons.B == ButtonState.Pressed)
            {
                md2.Pause();
                md2Weapon.Pause();
            }
            else if (kbState.IsKeyDown(Keys.R) ||
                     GamePad.GetState(PlayerIndex.One).Buttons.A == ButtonState.Pressed)
            {
                md2.Resume();
                md2Weapon.Resume();
            }
            // all code should be placed above this line
            base.Update(gameTime);
        }

        void drawMD2model(int iModel)
        {
            // 1: declare matrices
            Matrix matScale, matTranslation, matRotateY, matWorld;
            
            // 2: initialize matrices
            matScale        = Matrix.CreateScale(0.02f, 0.02f, 0.02f);
            matTranslation  = Matrix.CreateTranslation(0.0f, -0.4f, 3.0f);
            matRotateY      = Matrix.CreateRotationY((float)Math.PI / 2.0f);

            // 3: build cumulative world matrix using I.S.R.O.T. sequence
            // identity, scale, rotate, orbit(translate & rotate), translate
            matWorld = Matrix.Identity * matScale * matRotateY * matTranslation;

            // 4: set shader matrices, and texture
            mBE.Begin();
            mBE.World = matWorld;
            mBE.View = mMatView;
            mBE.Projection = mMatProj;
            if (iModel == WEAPON)
                mBE.Texture = md2Weapon.getTexture();
            else
                mBE.Texture = md2.getTexture();
            mBE.CommitChanges();

            // 5: draw object - select vertex type, data source, # of primitives
            gfx.GraphicsDevice.VertexDeclaration = mVertPosNormTex;
            foreach (EffectPass pass in mBE.CurrentTechnique.Passes)
            {
                pass.Begin();
                // get the data and draw it
                
               if (iModel == WEAPON)
               {
                    gfx.GraphicsDevice.Vertices[0].SetSource(
                    md2Weapon.vertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
                    gfx.GraphicsDevice.DrawPrimitives(
                    PrimitiveType.TriangleList, 0, md2Weapon.getNumTriangles());
               }
               else
               {
                    gfx.GraphicsDevice.Vertices[0].SetSource(
                    md2.vertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
                    gfx.GraphicsDevice.DrawPrimitives(
                    PrimitiveType.TriangleList, 0, md2.getNumTriangles());
               }
               pass.End();
            }
            mBE.End();
        }

        /// <summary>
        /// Set vertices for the ground.
        /// </summary>
        private void init_ground()
        {
            Vector2 uv  = new Vector2(0.0f, 0.0f);
            Vector3 pos = new Vector3(0.0f, 0.0f, 0.0f);
            Color color = Color.White;

            // set for vertices of surface with uv, pos, and color data
            uv.X = 10.0f; uv.Y = 10.0f; pos.X = -BOUNDARY; pos.Y = 0.0f; pos.Z = -BOUNDARY;
            mVertGround[0] = new VertexPositionColorTexture(pos, color, uv); //front right
            uv.X = 10.0f; uv.Y = 0.0f; pos.X = -BOUNDARY; pos.Y = 0.0f; pos.Z = BOUNDARY;
            mVertGround[1] = new VertexPositionColorTexture(pos, color, uv); //back right
            uv.X = 0.0f; uv.Y = 10.0f; pos.X = BOUNDARY; pos.Y = 0.0f; pos.Z = -BOUNDARY;
            mVertGround[2] = new VertexPositionColorTexture(pos, color, uv); //front left
            uv.X = 0.0f; uv.Y = 0.0f; pos.X = BOUNDARY; pos.Y = 0.0f; pos.Z = BOUNDARY;
            mVertGround[3] = new VertexPositionColorTexture(pos, color, uv); //back left
        }

        /// <summary>
        /// This method is called when the program begins to initialize shaders, to
        /// set the projection, and to set the render state.
        /// </summary>
        private void init_XNA_app()
        {
            // set window
            Window.Title = "Microsoft?XNA Game Studio Creator's Guide";

            // load BasicShader.fx and set global params
            mfx = content.Load<Effect>(@"shaders\BasicShader");
            worldViewProjParam = mfx.Parameters["fx_WVP"];
            mfx.CommitChanges();

            // load TextureShader.fx and set global params
            mfxTex     = content.Load<Effect>(@"shaders\TextureShader");
            mfxTex_WVP = mfxTex.Parameters["fx_WVP"];
            mfxTexture = mfxTex.Parameters["fx_Texture"];
            mfxTex.CommitChanges();

            // set camera matrix
            set_proj_matrix(); // only need to set at beginning of program
            gfx.GraphicsDevice.RenderState.CullMode = CullMode.None; // see both sides 
        }

        /// <summary>
        /// Draws textured ground.
        /// </summary>
        private void draw_ground()
        {
            // 1: declare matrices
            Matrix matIdentity, matTransl;

            // 2: initialize matrices
            matIdentity = Matrix.Identity; // always start with identity matrix
            matTransl   = Matrix.CreateTranslation(0.0f, -0.9f, 0.0f);

            // 3: build cumulative world matrix using I.S.R.O.T. sequence 
            // identity, scale, rotate, orbit(translate & rotate), translate
            mMatWorld   = matIdentity * matTransl;

            // 4: pass wvp matrix to shader
            mfxTex_WVP.SetValue(mMatWorld * mMatView * mMatProj);
            mfxTexture.SetValue(mTexGrass);
            mfxTex.CommitChanges();

            // 5: draw object - select vertex type, primitive type, # of primitives
            gfx.GraphicsDevice.VertexDeclaration = mVertPosColorTex;
            gfx.GraphicsDevice.DrawUserPrimitives<VertexPositionColorTexture>(
                PrimitiveType.TriangleStrip, mVertGround, 0, 2);
        }

        /// <summary>
        /// Adjusts view according to right thumbstick or mouse
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        Vector2 changeView(GameTime gameTime)
        {
            const float SENSITIVITY = 250.0f;

            // handle change in view using right and left keys
            KeyboardState kbState = Keyboard.GetState();

            int iWidthMiddle    = Window.ClientBounds.Width/2;
            int iHeightMiddle   = Window.ClientBounds.Height/2;

            Vector2 v2Change    = new Vector2(0.0f, 0.0f);

            if (mGamePadState[0].IsConnected == true) // gamepad on PC / Xbox
            {
                float kScaleY = (float)gameTime.ElapsedGameTime.Milliseconds/50.0f;
                v2Change.Y = kScaleY * mGamePadState[0].ThumbSticks.Right.Y * SENSITIVITY;
                v2Change.X = mGamePadState[0].ThumbSticks.Right.X * SENSITIVITY;
            }
            else                                     // mouse only (on PC) 
            {

#if !XBOX       // PC only has mouse which isn't recognized on xbox
                float kScaleY = (float)gameTime.ElapsedGameTime.Milliseconds / 100.0f;
                float kScaleX = (float)gameTime.ElapsedGameTime.Milliseconds/400.0f;

                // get cursor position
                Point point = GetCursorPoint();
                int iY      = point.Y;
                int iX      = point.X;

                // change X is cursor not at center of X
                if (iX != iWidthMiddle)
                {
                    v2Change.X  = iX - iWidthMiddle;
                    v2Change.X /= kScaleX;

                }
                // change Y is cursor not at center of Y
                if (iY != iHeightMiddle)
                {
                    v2Change.Y  = iY - iHeightMiddle;
                    v2Change.Y /= kScaleY;
                }
                // reset cursor back to center
                SetCursorPos(iWidthMiddle, iHeightMiddle);
#endif
            }
            return v2Change;
        }

        /// <summary>
        /// Define cone of visibility.  Set fov, aspect ratio, near clip / far clip
        /// </summary>
        void set_proj_matrix()
        {
            // define what our lense sees.  Params = fov, aspect ratio, near clip / far clip
            mMatProj = Matrix.CreatePerspectiveFieldOfView((float)Math.PI/4.0f,
                              (float)Window.ClientBounds.Width/(float)Window.ClientBounds.Height, 
                              0.005f, 1000.0f);
        }

        /// <summary>
        /// Define how objects are placed in our world relative to camera orientation.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        void set_view_matrix(GameTime gameTime)
        {
            Vector2 v2View = changeView(gameTime);
            cam.changeView(v2View.X, v2View.Y);
            mMatView = Matrix.CreateLookAt(cam.m_vPos, cam.m_vView, cam.m_vUp);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            // clear screen, set background, start drawing
            gfx.GraphicsDevice.Clear(Color.CornflowerBlue); 

            // begin shader - BasicShader.fx
            mfx.Begin();
            mfx.Techniques[0].Passes[0].Begin();

            // end shader - BasicShader.fx
            mfx.Techniques[0].Passes[0].End();
            mfx.End();

            // begin shader - TexturedShader.fx
            mfxTex.Begin();
            mfxTex.Techniques[0].Passes[0].Begin();

                // draw objects
                draw_ground();

            // end shader - TexturedShader.fx
            mfxTex.Techniques[0].Passes[0].End();
            mfxTex.End();
            drawMD2model(CHARACTER);
            drawMD2model(WEAPON);
            // stop drawing and present offscreen buffer
            base.Draw(gameTime);
        }
    }
}
